package org.xian.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @author : xian
 */
public class NIOClient implements Runnable {

    /**
     * 客户端的 selector
     */
    private Selector selector;
    /**
     * 客户端的 socketChannel
     */
    private SocketChannel socketChannel;

    private String host = "127.0.0.1";

    private int port = 8080;

    private boolean isStart = false;

    public NIOClient() {
        try {
            // 1、selector 和客户端的 socketChannel，同样设置为非阻塞
            this.selector = Selector.open();
            this.socketChannel = SocketChannel.open();
            this.socketChannel.configureBlocking(false);
            // 2、连接到服务端
            this.socketChannel.connect(new InetSocketAddress(host, port));
            // 3、注册到选择器, 并注册 OP_CONNECT 去连接服务端
            this.socketChannel.register(this.selector, SelectionKey.OP_CONNECT);
            this.isStart = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        NIOClient nioClient = new NIOClient();
        new Thread(nioClient).start();
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String input = scanner.nextLine();
            nioClient.sendMessage(input);
            if ("bye".equals(input)) {
                break;
            }
        }
    }

    @Override
    public void run() {
        // 这个线程处理 selector 的事件
        while (this.isStart) {
            try {
                // 1、这里阻塞到至少有一个事件就绪
                this.selector.select();
                Iterator<SelectionKey> selectionKeys = this.selector.selectedKeys().iterator();
                while (selectionKeys.hasNext()) {
                    SelectionKey selectionKey = selectionKeys.next();
                    selectionKeys.remove();
                    if (selectionKey.isValid()) {
                        // 2、判断是否和服务端建立好连接
                        if (selectionKey.isConnectable()) {
                            SocketChannel channel = (SocketChannel) selectionKey.channel();
                            if (!channel.finishConnect()) {
                                System.exit(-1);
                            }
                        }
                        // 3、处理服务端发来的数据
                        if (selectionKey.isReadable()) {
                            read(selectionKey);
                        }
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 发送消息
     */
    public void sendMessage(String message) {
        byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
        ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
        buffer.put(bytes);
        buffer.flip();
        try {
            this.socketChannel.register(this.selector, SelectionKey.OP_READ);
            this.socketChannel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void read(SelectionKey selectionKey) {
        // 处理服务端发送来的消息 和 服务端的处理差不多
        // 1、获得和服务端连接的，也就是 this.socketChannel
        // 当然客户端的 selector 管理多个连接还是 selectionKey.channel()
        // 2、创建 ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        try {
            // 3、读取数据，并返回已经读取的字节数
            int readBytes = this.socketChannel.read(buffer);
            if (readBytes > 0) {
                // buffer 切换到读模式
                buffer.flip();
                byte[] bytes = new byte[buffer.remaining()];
                buffer.get(bytes);
                String message = new String(bytes, StandardCharsets.UTF_8);
                System.out.println("Client Received --> " + message + ", From --> " + this.socketChannel.getRemoteAddress());
                // 4、关闭连接
                if ("bye".equals(message)) {
                    selectionKey.cancel();
                    this.socketChannel.close();
                    this.isStart = false;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
